package com.nplus.api.ipLimiter.handler;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.nplus.api.ipLimiter.annotation.IpLimiter;
import com.nplus.api.ipLimiter.error.IpLimiterError;
import com.nplus.cache.util.redis.RedisUtil;
import com.nplus.commons.util.WebUtil;
import com.nplus.framework.exception.WebException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpStatus;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

@Aspect
@Component
@Slf4j
public class IpLimiterHandler {

    @Autowired
    RedisUtil redisUtil;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    private HttpServletRequest request;

    private static String LIMITER_IP_TYPE = "IP";

    private static String LIMITER_ALL_TYPE = "all";

    /**
     * getRedisScript 读取脚本工具类
     * 这里设置为Long,是因为ipLimiter.lua 脚本返回的是数字类型
     */
    private DefaultRedisScript<Long> getRedisScript;

    @PostConstruct
    public void init() {
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        getRedisScript = new DefaultRedisScript<>();
        getRedisScript.setResultType(Long.class);
        getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/conf/spring/ipLimiter.lua")));
    }

    @Around("@annotation(ipLimiter)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, IpLimiter ipLimiter) throws Throwable {
        if (log.isDebugEnabled()) {
            log.debug("IpLimiterHandler[分布式限流处理器]开始执行限流操作");
        }
        Signature signature = proceedingJoinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("the Annotation @IpLimiter must used on method!");
        }
        /**
         * 获取注解参数
         */
        String limitIp = WebUtil.getIpAddr(request);
        Preconditions.checkNotNull(limitIp);
        long limitTimes = ipLimiter.limit();
        long expireTime = ipLimiter.time();
        String type = ipLimiter.type();
        if (log.isDebugEnabled()) {
            log.debug("IpLimiterHandler[分布式限流处理器]参数值为-limitTimes={},limitTimeout={}", limitTimes, expireTime);
        }

        /**
         * 执行Lua脚本
         */
        List<String> ipList = new ArrayList();
        if (type.equals(LIMITER_IP_TYPE)) {
            ipList.add(limitIp);
        } else {
            ipList.add(LIMITER_ALL_TYPE);
        }

        /**
         * 调用脚本并执行
         */
        Long result = (Long) redisTemplate.execute(getRedisScript, ipList, expireTime, limitTimes);
        if (result == 0) {
            throw new WebException(IpLimiterError.ERROR_AUTH_PARAM,
                    HttpStatus.FORBIDDEN);
        }
        if (log.isDebugEnabled()) {
            log.debug("IpLimiterHandler[分布式限流处理器]限流执行结果-result={},请求[正常]响应", result);
        }

        return proceedingJoinPoint.proceed();
    }
}
